/*
 *
 *  * Unidata Platform
 *  * Copyright (c) 2013-2020, UNIDATA LLC, All rights reserved.
 *  *
 *  * Commercial License
 *  * This version of Unidata Platform is licensed commercially and is the appropriate option for the vast majority of use cases.
 *  *
 *  * Please see the Unidata Licensing page at: https://unidata-platform.com/license/
 *  * For clarification or additional options, please contact: info@unidata-platform.com
 *  * -------
 *  * Disclaimer:
 *  * -------
 *  * THIS SOFTWARE IS DISTRIBUTED "AS-IS" WITHOUT ANY WARRANTIES, CONDITIONS AND
 *  * REPRESENTATIONS WHETHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE
 *  * IMPLIED WARRANTIES AND CONDITIONS OF MERCHANTABILITY, MERCHANTABLE QUALITY,
 *  * FITNESS FOR A PARTICULAR PURPOSE, DURABILITY, NON-INFRINGEMENT, PERFORMANCE AND
 *  * THOSE ARISING BY STATUTE OR FROM CUSTOM OR USAGE OF TRADE OR COURSE OF DEALING.
 *
 */

package org.unidata.mdm.rest.v1.dq.data.rendering;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;

import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import org.unidata.mdm.data.dto.GetRecordDTO;
import org.unidata.mdm.data.dto.UpsertRecordDTO;
import org.unidata.mdm.dq.data.dto.RecordGetQualityResult;
import org.unidata.mdm.dq.data.dto.RecordUpsertQualityResult;
import org.unidata.mdm.dq.data.type.search.RecordsDataQualityHeaderField;
import org.unidata.mdm.meta.type.search.EntityIndexType;
import org.unidata.mdm.rest.system.service.TypedRestInputRenderer;
import org.unidata.mdm.rest.system.service.TypedRestOutputRenderer;
import org.unidata.mdm.rest.v1.data.ro.atomic.AtomicDataGetResultRO;
import org.unidata.mdm.rest.v1.data.ro.atomic.AtomicDataUpsertResultRO;
import org.unidata.mdm.rest.v1.data.type.rendering.DataRestOutputRenderingAction;
import org.unidata.mdm.rest.v1.dq.data.converter.GetRecordDataQualityConverter;
import org.unidata.mdm.rest.v1.dq.data.converter.UpsertRecordDataQualityConverter;
import org.unidata.mdm.rest.v1.dq.data.module.DataQualityDataRestModule;
import org.unidata.mdm.rest.v1.dq.data.ro.GetRecordsDataQualityResultRO;
import org.unidata.mdm.rest.v1.dq.data.ro.SearchRecordsDataQualityRequestRO;
import org.unidata.mdm.rest.v1.dq.data.ro.UpsertRecordsDataQualityResultRO;
import org.unidata.mdm.rest.v1.search.ro.SearchRequestRO;
import org.unidata.mdm.rest.v1.search.type.rendering.SearchRestInputRenderingAction;
import org.unidata.mdm.search.context.ComplexSearchRequestContext;
import org.unidata.mdm.search.context.ComplexSearchRequestContext.ComplexSearchRequestContextBuilder;
import org.unidata.mdm.search.context.NestedSearchRequestContext;
import org.unidata.mdm.search.context.SearchRequestContext;
import org.unidata.mdm.search.type.form.FieldsGroup;
import org.unidata.mdm.search.type.form.FormField;
import org.unidata.mdm.search.type.query.SearchQuery;
import org.unidata.mdm.system.context.InputCollector;
import org.unidata.mdm.system.type.rendering.FieldDef;
import org.unidata.mdm.system.type.rendering.FragmentDef;
import org.unidata.mdm.system.type.rendering.InputFragmentRenderer;
import org.unidata.mdm.system.type.rendering.InputRenderingAction;
import org.unidata.mdm.system.type.rendering.MapInputSource;
import org.unidata.mdm.system.type.rendering.OutputFragmentRenderer;
import org.unidata.mdm.system.type.rendering.OutputRenderingAction;
import org.unidata.mdm.system.type.rendering.RenderingProvider;

/**
 * @author Alexey Tsarapkin
 */
@Component
public class DataQualityDataRenderingProvider implements RenderingProvider {
    /**
     * <->
     */
    private static final UpsertRecordDataQualityConverter UPSERT_DATA_QUALITY_RECORDS_CONVERTER =
            new UpsertRecordDataQualityConverter();
    /**
     * <->
     */
    private static final GetRecordDataQualityConverter GET_DATA_QUALITY_RECORDS_CONVERTER =
            new GetRecordDataQualityConverter();
    /**
     * {@inheritDoc}
     */
    @Override
    public Collection<InputFragmentRenderer> get(InputRenderingAction action) {

        if (SearchRestInputRenderingAction.SIMPLE_SEARCH_INPUT == action) {
            return Collections.singletonList(new RecordsDataQualitySearchRequestRenderer());
        }

        return Collections.emptyList();
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public Collection<OutputFragmentRenderer> get(OutputRenderingAction action) {

        if (DataRestOutputRenderingAction.ATOMIC_UPSERT_OUTPUT == action) {
            return Collections.singletonList(new RecordsDataQualityUpsertResultRenderer());
        } else if (DataRestOutputRenderingAction.ATOMIC_GET_OUTPUT == action) {
            return Collections.singletonList(new RecordsDataQualityGetResultRenderer());
        }

        return Collections.emptyList();
    }
    /**
     * @author Mikhail Mikhailov on Apr 2, 2021
     * Upsert renderer.
     */
    private static class RecordsDataQualityUpsertResultRenderer extends TypedRestOutputRenderer<AtomicDataUpsertResultRO, UpsertRecordDTO> {

        public RecordsDataQualityUpsertResultRenderer() {
            super(  AtomicDataUpsertResultRO.class,
                    UpsertRecordDTO.class,
                    Collections.singletonList(FieldDef.fieldDef(DataQualityDataRestModule.MODULE_ID, UpsertRecordsDataQualityResultRO.class)));
        }

        @Override
        protected Map<String, Object> renderFragmentFields(FragmentDef fragmentDef, UpsertRecordDTO container) {
            return Collections.singletonMap(
                    DataQualityDataRestModule.MODULE_ID,
                    UPSERT_DATA_QUALITY_RECORDS_CONVERTER.to(container.fragment(RecordUpsertQualityResult.ID)));
        }
    }
    /**
     * @author Mikhail Mikhailov on Apr 2, 2021
     * Get renderer.
     */
    private static class RecordsDataQualityGetResultRenderer extends TypedRestOutputRenderer<AtomicDataGetResultRO, GetRecordDTO> {

        public RecordsDataQualityGetResultRenderer() {
            super(  AtomicDataGetResultRO.class,
                    GetRecordDTO.class,
                    Collections.singletonList(FieldDef.fieldDef(DataQualityDataRestModule.MODULE_ID, GetRecordsDataQualityResultRO.class)));
        }

        @Override
        protected Map<String, Object> renderFragmentFields(FragmentDef fragmentDef, GetRecordDTO container) {
            return Collections.singletonMap(
                    DataQualityDataRestModule.MODULE_ID,
                    GET_DATA_QUALITY_RECORDS_CONVERTER.to(container.fragment(RecordGetQualityResult.ID)));
        }
    }
    /**
     * @author Mikhail Mikhailov on Apr 2, 2021
     * Search renderer.
     */
    private static class RecordsDataQualitySearchRequestRenderer extends TypedRestInputRenderer<SearchRequestRO> {

        public RecordsDataQualitySearchRequestRenderer() {
            super(  SearchRequestRO.class,
                    Collections.singletonList(FieldDef.fieldDef(DataQualityDataRestModule.MODULE_ID, SearchRecordsDataQualityRequestRO.class)));
        }

        @Override
        protected void renderFields(FragmentDef fragmentDef, InputCollector collector, SearchRequestRO source, MapInputSource fieldValues) {

            SearchRecordsDataQualityRequestRO input
                = fieldValues.get(DataQualityDataRestModule.MODULE_ID, SearchRecordsDataQualityRequestRO.class);

            if (Objects.nonNull(input)) {

                List<FormField> fields = processFields(input);
                if (CollectionUtils.isEmpty(fields)) {
                    return;
                }

                ComplexSearchRequestContextBuilder top = (ComplexSearchRequestContextBuilder) collector;
                ComplexSearchRequestContext temp = top.build();

                for (SearchRequestContext sCtx : temp.getAllContexts()) {
                    if (sCtx.getType() == EntityIndexType.RECORD) {
                        top.nested(sCtx, NestedSearchRequestContext.objects(SearchRequestContext.builder(EntityIndexType.RECORD, sCtx.getEntity(), sCtx.getStorageId())
                                             .nestedPath(RecordsDataQualityHeaderField.FIELD_QUALITY_ERRORS.getPath())
                                             .query(SearchQuery.formQuery(FieldsGroup.and(fields)))
                                             .count(1000)
                                             .build())
                                         .nestedQueryName(RecordsDataQualityHeaderField.FIELD_QUALITY_ERRORS.getPath())
                                         .build());
                    }
                }
            }
        }

        private List<FormField> processFields(SearchRecordsDataQualityRequestRO input) {

            List<FormField> fields = new ArrayList<>();
            if (CollectionUtils.isNotEmpty(input.getSetNames())) {
                fields.add(FormField.exact(RecordsDataQualityHeaderField.FIELD_SET_NAME, input.getSetNames()));
            }

            if (CollectionUtils.isNotEmpty(input.getRuleNames())) {
                fields.add(FormField.exact(RecordsDataQualityHeaderField.FIELD_RULE_NAME, input.getRuleNames()));
            }

            if (CollectionUtils.isNotEmpty(input.getFunctionNames())) {
                fields.add(FormField.exact(RecordsDataQualityHeaderField.FIELD_FUNCTION_NAME, input.getFunctionNames()));
            }

            if (StringUtils.isNotBlank(input.getCategory())) {
                fields.add(FormField.exact(RecordsDataQualityHeaderField.FIELD_CATEGORY, input.getCategory()));
            }

            if (StringUtils.isNotBlank(input.getSeverity())) {
                fields.add(FormField.exact(RecordsDataQualityHeaderField.FIELD_SEVERITY, input.getSeverity()));
            }

            if (input.getTotalScore() > 0) {
                fields.add(FormField.range(RecordsDataQualityHeaderField.FIELD_TOTAL_SCORE, Integer.valueOf(input.getTotalScore()), null));
            }

            if (StringUtils.isNotBlank(input.getMessage())) {
                fields.add(FormField.fuzzy(RecordsDataQualityHeaderField.FIELD_MESSAGE, input.getMessage()));
            }

            return fields;
        }
        /**
         * {@inheritDoc}
         */
        @Override
        public int order() {
            return 1000;
        }
    }
}
